# -*- coding: utf-8 -*-
#
# Copyright (c) 2007 - 2014 -- Lars Heuer - Semagia <http://www.semagia.com/>.
# All rights reserved.
#
# BSD license.
#
"""\
This module provides a ``TopicMapWriter`` which writes 
`XML Topic Maps (XTM) 1.0 <http://www.topicmaps.org/xtm/1.0/>`_

:author:       Lars Heuer (heuer[at]semagia.com)
:organization: Semagia - http://www.semagia.com/
:license:      BSD license
"""
from tm.xmlutils import XMLWriter
from mappa.utils import is_default_name_type
from mappa._internal.it import no
from mappa._internal.utils import topic_id
from mappa import XSD, voc
_NS_XLINK = voc.XLINK
_NS_XTM_10 = voc.XTM_10
del voc


class XTM10TopicMapWriter(object):
    """\
    The XTM 1.0 writer.
    """
    def __init__(self, out, base, encoding='utf-8', version=None):
        if not out:
            raise TypeError('"out" is not specified')
        if version and version != 1.0:
            raise ValueError('Unexpected version number: "%s"' % str(version))
        if not base:
            raise TypeError('"base" is not specified')
        self._writer = None    # XMLWriter instance to serialize a topic map
        self._out = out
        self._encoding = encoding
        self.export_iids = True
        self.prettify = False
        self._base = base

    def write(self, topicmap):
        """\
        Serializes the specified ``topicmap``.
        """
        self._writer = XMLWriter(self._out, self._encoding)
        writer = self._writer
        writer.prettify = self.prettify
        writer.startDocument()
        writer.comment(u'Generated by Mappa - http://mappa.semagia.com/')
        attrs = {u'xmlns': _NS_XTM_10, u'xmlns:xlink': _NS_XLINK}
        self._add_id(attrs, topicmap)
        writer.startElement(u'topicMap', attrs)
        write_topic = self._write_topic
        for topic in topicmap.topics:
            write_topic(topic)
        write_assoc = self._write_association
        for assoc in topicmap.associations:
            write_assoc(assoc)
        writer.endElement(u'topicMap')
        writer.endDocument()

    def _write_topic(self, topic):
        """\
        Serializes a topic and its characteristics.
        
        `topic`
            The topic to serialize
        """
        sids, slos = tuple(topic.sids), tuple(topic.slos)
        if is_default_name_type(topic) and _is_omitable(topic, sids, slos):
            return
        self._writer.startElement(u'topic', {u'id': topic_id(self._base, topic)})
        self._write_identities(topic, sids, slos)
        write_name = self._write_name
        for name in topic.names:
            write_name(name)
        write_occurrence = self._write_occurrence
        for occ in topic.occurrences:
            write_occurrence(occ)
        self._writer.endElement(u'topic')

    def _write_occurrence(self, occ):
        """\
        Serializes the occurrence.

        `occurrence`
            An occurrence.
        """
        self._writer.startElement(u'occurrence', self._reifier(occ))
        self._write_type(occ)
        self._write_scope(occ)
        self._write_data(occ)
        self._writer.endElement(u'occurrence')

    def _write_name(self, name):
        """\
        Serializes the topic name and its variants.

        `name`
            A name.
        """
        self._writer.startElement(u'baseName', self._reifier(name))
        self._write_scope(name)
        self._writer.dataElement(u'baseNameString', name.value)
        write_variant = self._write_variant
        for variant in name.variants:
            write_variant(variant)
        self._writer.endElement(u'baseName')

    def _write_variant(self, variant):
        """\
        Serializes a variant.
        """
        startElement, endElement = self._writer.startElement, self._writer.endElement
        startElement(u'variant', self._reifier(variant))
        startElement(u'parameters')
        write_topic_ref = self._write_topic_ref
        for theme in variant.scope:
            write_topic_ref(theme)
        endElement(u'parameters')
        startElement(u'variantName')
        self._write_data(variant)
        endElement(u'variantName')
        endElement(u'variant')

    def _write_association(self, assoc):
        """\
        Serializes an association.

        `association`
            An association.
        """
        roles = tuple(assoc.roles)
        if not roles:
            #TODO: Warning
            return
        self._writer.startElement(u'association', self._reifier(assoc))
        self._write_type(assoc)
        self._write_scope(assoc)
        write_role = self._write_role
        for role in roles:
            write_role(role)
        self._writer.endElement(u'association')

    def _write_role(self, role):
        """\
        Serializes a role.
        """
        startElement, endElement = self._writer.startElement, self._writer.endElement
        write_topic_ref = self._write_topic_ref
        startElement(u'member', self._reifier(role))
        startElement(u'roleSpec')
        write_topic_ref(role.type)
        endElement(u'roleSpec')
        write_topic_ref(role.player)
        endElement(u'member')

    def _write_data(self, datatyped):
        """\
        Serializes the ``value`` property of an occurrence or variant.

        `datatyped`
            Either an occurrence instance or a variant instance.
        """
        dt = datatyped.datatype
        if dt == XSD.anyURI:
            self._writer.emptyElement(u'resourceRef', self._href(datatyped.value))
        else:
            if dt != XSD.string:
                #TODO: Warning
                pass
            self._writer.dataElement(u'resourceData', datatyped.value)

    def _write_locators(self, name, locs):
        emptyElement = self._writer.emptyElement
        href = self._href
        for loc in locs:
            emptyElement(name, href(loc))

    def _write_type(self, typed):
        """\
        Serizalizes the ``type`` of a typed Topic Maps construct.

        `typed`
            A typed Topic Maps construct (association, role, occurrence, name).
        """
        self._writer.startElement(u'instanceOf')
        self._write_topic_ref(typed.type)
        self._writer.endElement(u'instanceOf')

    def _write_scope(self, scoped):
        """\
        Serizalizes the ``scope`` of the `scoped` Topic Maps construct.

        `scoped`
            The scoped Topic Maps construct (association, occurrence, name, variant)
        """
        write_topic_ref = self._write_topic_ref
        written = False
        for i, theme in enumerate(scoped.scope):
            if i == 0:
                self._writer.startElement(u'scope')
                written = True
            write_topic_ref(theme)
        if written:
            self._writer.endElement(u'scope')

    def _write_topic_ref(self, topic):
        """\
        Writes a ``<topicRef xlink:href="#topic-ref"/>`` element.
        """
        self._writer.emptyElement(u'topicRef', self._href(u'#%s' % topic_id(self._base, topic)))

    def _write_identities(self, topic, sids, slos):
        """\
        
        """
        reifiable = topic.reified
        if not reifiable and not sids and not slos:
            return
        writer = self._writer
        href = self._href
        writer.startElement(u'subjectIdentity')
        if slos:
            if len(slos) > 1:
                #LOG.warning("The topic " + topic.id + " has more than one subject locator, exporting just one")
                pass
            # Choose one subject locator
            writer.emptyElement(u'resourceRef', href(slos[0]))
        for sid in sids:
            writer.emptyElement(u'subjectIndicatorRef', href(sid))
        if reifiable:
            writer.emptyElement(u'subjectIndicatorRef', href(u'#%s' % self._reifiable_id(reifiable)))
        writer.endElement(u'subjectIdentity')

    def _reifier(self, reifiable):
        attrs = {}
        self._add_id(attrs, reifiable)
        return attrs

    def _reifiable_id(self, reifiable):
        return u'reifier-%s' % topic_id(self._base, reifiable.reifier)

    def _add_id(self, attrs, reifiable):
        """\
        Adds an "id" attribute to `attrs` iff ``reifiable`` is reified.
        """
        if reifiable.reifier:
            attrs.update({u'id': self._reifiable_id(reifiable)})

    def _href(self, iri):
        """\
        Returns a ``{'xlink:href': iri}`` `dict`.
        """
        return {u'xlink:href': iri}


def _is_omitable(topic, sids, slos):
    """\
    Returns if the `topic` has just one identity and no further 
    characteristics (occurrences, names).
    """
    # No need to check 'roles played' and 'reified' here since
    # the reified statement refers to the topic already and
    # the roles played are serialized through the associations
    return len(sids) + len(slos) <= 1 \
            and no(topic.names) and no(topic.occurrences)
